/*
 *  Copyright 2007 Blandware (http://www.blandware.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.blandware.atleap.common.parsers.openoffice;

import com.blandware.atleap.common.parsers.SpecificPlainTextExtractor;
import com.blandware.atleap.common.parsers.exception.PlainTextExtractorException;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.InputStream;
import java.io.Writer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * An extractor that extracts a plain text from OpenOffice (and StarOffice)
 * documents. Only XML-based formats are understood.
 * This class is mainly copied from JackRabbit extractor.
 * 
 * <p><a href="OpenOfficePlainTextExtractor.java.html"><i>View source</i></a></p>
 *
 * @author Roman Puchkovskiy <a href="mailto:roman.puchkovskiy@blandware.com">
 *         &lt;roman.puchkovskiy@blandware.com&gt;</a>
 * @version : $Revision: 1.2 $ : 22.12.2007 18:43:26 $
 * @see com.blandware.atleap.common.parsers.SpecificPlainTextExtractor
 */
public class OpenOfficePlainTextExtractor implements SpecificPlainTextExtractor {
    /**
     * Extracts a plain text from an OpenOffice XML-based document.
     *
     * @param input the input stream that supplies document for extraction
     * @param output the writer that will accept the extracted text
     * @param encoding ignored
     * @throws PlainTextExtractorException throwed on exception raised during
     * extracting
     */
    public void extract(InputStream input, Writer output, String encoding)
            throws PlainTextExtractorException {
        try {
            SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
            saxParserFactory.setValidating(false);
            SAXParser saxParser = saxParserFactory.newSAXParser();
            XMLReader xmlReader = saxParser.getXMLReader();
            xmlReader.setFeature("http://xml.org/sax/features/validation", false);
            xmlReader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);

            ZipInputStream zis = new ZipInputStream(input);
            ZipEntry ze = zis.getNextEntry();
            while (!ze.getName().equals("content.xml")) {
                ze = zis.getNextEntry();
            }

            OpenOfficeContentHandler contentHandler =
                    new OpenOfficeContentHandler();
            xmlReader.setContentHandler(contentHandler);
            try {
                xmlReader.parse(new InputSource(zis));
            } finally {
                zis.close();
            }

            output.write(compressWhitespace(contentHandler.getContent()));
        } catch (Exception e) {
            throw new PlainTextExtractorException(e);
        }
    }

    /**
     * @see com.blandware.atleap.common.parsers.SpecificPlainTextExtractor#getUsedEncoding()
     */
    public String getUsedEncoding() {
        return null;
    }

    /**
     * Compresses whitespace: every sequence of whitespaces is replaced with
     * a single whitespace character.
     *
     * @param s     string to process
     * @return compressed string
     */
    private String compressWhitespace(String s) {
        StringBuffer buffer = new StringBuffer();
        // setting to true to remove heading whitespace
        boolean lastIsWhitespace = true;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            boolean isWhitespace = Character.isWhitespace(c);
            if (!isWhitespace) {
                // not whitespace character: write it
                buffer.append(c);
            } else if (!lastIsWhitespace) {
                // whitespace character, while last was not: so, this is first
                // whitespace in sequence, write it
                buffer.append(c);
            }
            lastIsWhitespace = isWhitespace;
        }
        return buffer.toString();
    }

    /**
     * Handler that extracts text.
     */
    private class OpenOfficeContentHandler extends DefaultHandler {

        private StringBuffer content;
        private boolean appendChar;

        public OpenOfficeContentHandler() {
            content = new StringBuffer();
            appendChar = false;
        }

        /**
         * Returns the text content extracted from parsed content.xml.
         *
         * @return text content as string
         */
        public String getContent() {
            return content.toString();
        }

        public void startElement(String namespaceURI, String localName,
                                 String rawName, Attributes atts)
                throws SAXException {
            if (rawName.startsWith("text:")) {
                appendChar = true;
            }
        }

        public void characters(char[] ch, int start, int length)
                throws SAXException {
            if (appendChar) {
                content.append(ch, start, length).append(" ");
            }
        }

        public void endElement(java.lang.String namespaceURI,
                               java.lang.String localName,
                               java.lang.String qName)
                throws SAXException {
            appendChar = false;
        }
    }
}
